package org.barad.architecture.analyzer.archpad.persistence;

import java.beans.BeanInfo;
import java.beans.DefaultPersistenceDelegate;
import java.beans.Encoder;
import java.beans.ExceptionListener;
import java.beans.Expression;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PersistenceDelegate;
import java.beans.PropertyDescriptor;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;

import org.barad.architecture.analyzer.archpad.component.ArchPadComponent;
import org.jgraph.graph.AbstractCellView;
import org.jgraph.graph.DefaultEdge;
import org.jgraph.graph.DefaultGraphCell;
import org.jgraph.graph.DefaultPort;
import org.jgraph.graph.GraphConstants;

/**
 * This class manages the persistence of ArchPad models and
 * library components. It is responsible for persisting to 
 * and loading from files.
 * 
 * @author svetoslavganov@gmail.com (Svetoslav R. Ganov)
 *
 */
public class PersistenceManager {
	private ClassLoader classLoader;
	
	/**
	 * Creates a new instance.
	 * 
	 * TODO: Implement the singleton pattern.
	 */
	public PersistenceManager() {
		this.classLoader = Thread.currentThread().getContextClassLoader();
	}

	/**
	 * Persists an array of graph cells given an {@link OutputStream}. This
	 * method is exploiting the Java XML persistence rather serialization.
	 * 
	 * @param selectedCells The cells.
	 * @param outputStream The {@link OutputStream}.
	 */
	public void persisistCells(Object[] selectedCells, OutputStream outputStream) {		
		XMLEncoder encoder;
		try {
			encoder = new XMLEncoder(outputStream);
			encoder.setExceptionListener(new ExceptionListener() {
				public void exceptionThrown(Exception e) {
					e.printStackTrace();
				}
			});
			PersistenceDelegate persistenceDelegate = null;
			DefaultPersistenceDelegate delegate = null;
			delegate = new DefaultPersistenceDelegate(new String[] { "userObject" });
			encoder.setPersistenceDelegate(DefaultGraphCell.class, delegate);
			delegate = new DefaultPersistenceDelegate(new String[] { "userObject" });
			encoder.setPersistenceDelegate(DefaultEdge.class, delegate);
			delegate = new DefaultPersistenceDelegate(new String[] { "userObject" });
			encoder.setPersistenceDelegate(DefaultPort.class, delegate);
			delegate = new DefaultPersistenceDelegate(new String[] { "cell", "attributes" });
			encoder.setPersistenceDelegate(AbstractCellView.class, delegate);			
			persistenceDelegate = new PersistenceDelegate() {
				protected Expression instantiate(Object oldInstance, Encoder out) {
					return new Expression(oldInstance, GraphConstants.class, "getROUTING_SIMPLE", null);
				}
			};
			encoder.setPersistenceDelegate(DefaultEdge.DefaultRouting.class, persistenceDelegate);
			persistenceDelegate = new PersistenceDelegate() {
				protected Expression instantiate(Object oldInstance, Encoder out) {
					return new Expression(oldInstance, GraphConstants.class, "getROUTING_DEFAULT", null);
				}
			};
			encoder.setPersistenceDelegate(DefaultEdge.LoopRouting.class, persistenceDelegate);
			encoder.setPersistenceDelegate(ArrayList.class, encoder.getPersistenceDelegate(List.class));
			encoder.writeObject(selectedCells);
			encoder.close();
		} catch (Exception e) {
			JOptionPane.showMessageDialog(null, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
		}
	}

	/**
	 * Loads a set of graph cells from an {@link InputStream}.
	 * This method assumes that the stream contains graph cells
	 * that are stored via the Java XML persistence.
	 * 
	 * @param inputStream The {@link InputStream).
	 * @return An array of loaded cells.
	 */
	public Object[] loadCells(InputStream inputStream) {
		XMLDecoder decoder = new XMLDecoder(inputStream);
		Object[] loadedCells = null;
		if (decoder != null) {
			loadedCells = (Object[])decoder.readObject();
			decoder.close();
		}
		for (Object obj: loadedCells) {
			DefaultGraphCell cell = (DefaultGraphCell)obj;
			loadIcons(cell);
		}
		return loadedCells;
	}

	/**
	 * Makes a given class property transient, i.e. will not be persisted.
	 * 
	 * @param clazz The {@link Class}.
	 * @param field The name of the field (property).
	 */
	public static void makeTransient(Class<?> clazz, String field) {
		try {
			BeanInfo info = Introspector.getBeanInfo(clazz);
			PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors();
			for (int i = 0; i < propertyDescriptors.length; ++i) {
				PropertyDescriptor pd = propertyDescriptors[i];
				if (pd.getName().equals(field)) {
					pd.setValue("transient", Boolean.TRUE);
				}
			}
		} catch (IntrospectionException e) {
			//TODO: Implement the error handling.
		}
	}
	
	/**
	 * Loads the icons for a graph cell and all of its
	 * children cells. This is repeated recursively until
	 * leaf cells are reached. The icons are not persisted, 
	 * rather their file names.
	 * 
	 * @param parent The parent graph cell.
	 */
	@SuppressWarnings("unchecked")
	private void loadIcons(DefaultGraphCell parent) {
		List<DefaultGraphCell> children = new LinkedList<DefaultGraphCell>();
		children.addAll(parent.getChildren());
		while (children.size() > 0) {
			DefaultGraphCell child = children.remove(0);
			loadIcon(child);
			children.addAll(child.getChildren());
		}
	}
	
	/**
	 * Loads the icon for a graph cell.
	 * 
	 * @param cell The graph cell.
	 */
	@SuppressWarnings("unchecked")
	private void loadIcon(DefaultGraphCell cell) {
		ArchPadComponent archPadComponent = (ArchPadComponent)cell.getUserObject();
		if (archPadComponent != null) {
			String iconFileName = archPadComponent.getIconFileName();
			if (iconFileName != null) {
				URL url = classLoader.getResource(iconFileName);
				Icon icon = new ImageIcon(url);
				archPadComponent.setIcon(icon);
				cell.getAttributes().put(GraphConstants.ICON, icon);
			}
		}
	}
}
